using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;

using Gtk;
using Mono.Cecil;
using MonoDevelop.Components.Commands;
using MonoDevelop.Core;
using MonoDevelop.Core.Gui.Dialogs;
using MonoDevelop.Projects;
using MonoDevelop.Projects.CodeGeneration;
using MonoDevelop.Projects.Parser;
using MonoDevelop.Projects.Text;
using MonoDevelop.Ide.Gui;
using MonoDevelop.Ide.Gui.Pads;
using MonoDevelop.Ide.Gui.Content;
using MonoDevelop.Ide.Tasks;



namespace MonoDevelop.SmokeyAddin
{
        internal static class ViolationParser
        {
                public static Violation Parse (XmlNode errorNode)
                {
                        if (errorNode.Name != "Error") throw new ArgumentException ("errorNode");
                        Violation v = new Violation ();
                        ViolationParser.ParseViolationData (v, errorNode.SelectSingleNode ("Violation"));
                        ViolationParser.ParseLocationsData (v.Locations, errorNode.SelectSingleNode ("Location"));                        
                        return v;
                }
               
                private static void ParseViolationData (Violation v, XmlNode violationNode)
                {
                        string severity = violationNode.Attributes ["severity"].Value;
                        if (severity == "Error")
                                v.Severity = Severity.Error;
                        else if (severity == "Warning")
                                v.Severity = Severity.Warning;
                        else if (severity == "Nitpick")
                                v.Severity = Severity.Nitpick;
                        else
                                throw new CouldNotParseSmokeyOutputException ();
                        v.Cause = violationNode.SelectSingleNode ("Cause/text ()").Value;
                        v.Description = violationNode.SelectSingleNode ("Description/text ()").Value;
                        v.Fix = violationNode.SelectSingleNode ("Fix/text ()").Value;
                }
                
                private static void ParseLocationsData (IList<Location> locations, XmlNode locationNode)
                {
                        string locationName = locationNode.Attributes ["name"].Value;
                        string locationDetails = locationNode.Attributes ["details"].Value;
                        ViolationParser.InferLocations (locations, locationName, locationDetails);                        
                        if (locations.Count <= 1) { // we might already have some info provided by Smokey for a single file
                                Location currentLocation;
                                if (locations.Count == 0) {
                                        currentLocation = new Location ();
                                        locations.Add (currentLocation);
                                } else {
                                        currentLocation = locations [0];
                                }
                                if (locationNode.Attributes ["file"] != null
                                    && !string.IsNullOrEmpty (locationNode.Attributes ["file"].Value)) {
                                        string fileName = locationNode.Attributes ["file"].Value;
                                                if (fileName.IndexOf (' ') != -1) // split across multiple files
                                                        fileName = fileName.Split (' ') [0]; // first one
                                        currentLocation.FileName = fileName;
                                }
                                if (locationNode.Attributes ["line"] != null
                                    && !string.IsNullOrEmpty (locationNode.Attributes ["line"].Value)) {
                                        int line = currentLocation.Line;
                                        if (locationNode.Attributes ["line"] != null)
                                                int.TryParse (locationNode.Attributes ["line"].Value, out line);
                                        currentLocation.Line = line;
                                }                        
                        }
                }
                
                private static void InferLocations (IList<Location> locations, string locationName, string locationDetails)
                {
                        string typeName = string.Empty;
                        if (locationName.StartsWith ("Type: ")) { // we have type name
                                typeName = locationName.Remove (0, 6);
                                Location l = new Location ();
                                if (ViolationParser.InferLocationByTypeName (ref l, typeName))                                
                                        locations.Add (l);
                        } else if (locationName.StartsWith ("Assembly: ")) { // assembly-wide violation
                                // it can be set of methods, or something applying to the assembly itself
                                if (string.IsNullOrEmpty (locationDetails)) { // try loading AssemblyInfo.*
                                        string assemblyInfo =
                                                Inspector.CurrentProject.LanguageBinding.GetFileName ("AssemblyInfo");
                                        Location l = new Location ();
                                        if (ViolationParser.InferLocationByRelativeFileName (ref l, assemblyInfo))
                                                locations.Add (l);
                                } else if (locationDetails.StartsWith ("Methods: ")) { // set of methods
                                        string methodsString = locationDetails.Remove (0, 9);
                                        string [] methodSigs = methodsString.Split (new string [] { char.ConvertFromUtf32 (0xA) }, StringSplitOptions.RemoveEmptyEntries);
                                        foreach (string methodSig in methodSigs) {                                              
                                                Location l = new Location ();
                                                if (ViolationParser.InferLocationByMethodSignature (ref l, methodSig)) 
                                                        locations.Add (l);
                                        }
                                } else if (locationDetails.StartsWith ("Unused: ")) { // unused classes
                                        string typesString = locationDetails.Remove (0, 8);
                                        string [] types = typesString.Split (new string [] { char.ConvertFromUtf32 (0xA) }, StringSplitOptions.RemoveEmptyEntries);
                                        foreach (string type in types) {
                                                Location l = new Location ();
                                                if (ViolationParser.InferLocationByTypeName (ref l, type))
                                                        locations.Add (l);
                                        }
                                }
                        } else if (locationName.StartsWith ("Method: ")) { // method signature follows
                                string methodSig = locationName.Remove (0, 8);                        
                                Location l = new Location ();
                                if (ViolationParser.InferLocationByMethodSignature (ref l, methodSig))
                                        locations.Add (l);
                        }
                }
                
                // parse stuff like "Full.Return.Type TypeName::MethodName(ArgType,ArgType2)"
                private static bool InferLocationByMethodSignature (ref Location location, string methodSignature)
                {
                        Regex regex = new Regex (@"([^\s]+)\s([^:]+)::([^\(]+)\(([^,\)]+)?(,[^,\)]+)*\)");
                        Match match = regex.Match (methodSignature);
                        string returnType = match.Groups [1].Value;
                        string typeName = match.Groups [2].Value; 
                        string methodName = match.Groups [3].Value;
                        List<string> parameters = new List<string> ();
                        if (match.Groups [4] != null) // first argument
                                parameters.Add (match.Groups [4].Value);
                        if (match.Groups [5] != null) // have more than one argument
                                foreach (Capture c in match.Groups [5].Captures)
                                        parameters.Add (c.Value.Remove (0, 1)); // remove "," before the parameter name
                        if (ViolationParser.InferLocationByTypeName (ref location, typeName)) {
                                IClass c = IdeApp.ProjectOperations.ParserDatabase
                                        .GetProjectParserContext (Inspector.CurrentProject)
                                        .GetClass (typeName);
                                // FIXME: (?) seems like this doesn't find constructors (methodName = '.ctor')
                                foreach (IMethod m in c.Methods) { // look for the specified method
                                        if (m.Name == methodName && m.ReturnType.FullyQualifiedName == returnType
                                            && m.Parameters.Count == parameters.Count) { // make sure it is not an overload
                                                bool passed = true;
                                                for (int i = 0; i < m.Parameters.Count; i++) {
                                                        if (parameters [i] != m.Parameters [i].ReturnType.FullyQualifiedName) {
                                                                passed = false;
                                                                break;
                                                        }
                                                }
                                                if (passed) {
                                                        location.Column = m.Region.BeginColumn;
                                                        location.Line = m.Region.BeginLine;
                                                        return true;
                                                }                                              
                                        }
                                }
                                return true;
                        }
                        return false;
                }
                
                private static bool InferLocationByTypeName (ref Location location, string typeName)
                {
                        if (!string.IsNullOrEmpty (typeName)) {
                                IClass c = IdeApp.ProjectOperations.ParserDatabase
                                        .GetProjectParserContext (Inspector.CurrentProject)
                                        .GetClass (typeName);
                                if (c != null) {
                                        location.Column = c.Region.BeginColumn;
                                        location.FileName = c.Region.FileName;
                                        location.Line = c.Region.BeginLine;
                                        return true;
                                } else {
                                        return false;
                                }
                        } else {
                                return false;
                        }
                }
                
                private static bool InferLocationByRelativeFileName (ref Location location, string fileName)
                {
                        string fullPath = Inspector.CurrentProject.BaseDirectory
                                + Path.DirectorySeparatorChar
                                + fileName;
                        if (File.Exists (fullPath)) {
                                location.FileName = fullPath;
                                return true;
                        } else {
                                return false;
                        }
                }
                
        }
}
